package com.demo.webapp.ui.paging.taglib;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.context.support.WebApplicationContextUtils;

import com.demo.core.dao.base.support.PageSupport;
import com.demo.core.service.exception.PageSupportNotFoundException;
import com.demo.webapp.ui.Component;
import com.demo.webapp.ui.ComponentBase;
import com.demo.webapp.ui.DisplayerConfig;
import com.demo.webapp.ui.displayer.Displayer;
import com.demo.webapp.ui.displayer.DisplayerMapping;
import com.demo.webapp.ui.displayer.MessageResourcesDisplayer;
import com.demo.webapp.ui.menu.taglib.MenuDisplayerTag;
import com.demo.webapp.ui.paging.PageComponent;
import com.demo.webapp.ui.paging.PageRepositoryResource;
import com.opensymphony.xwork2.ActionContext;

/**
 * 
 * Page的JSP标签
 * 使用标签 example for：
 * <pre>
 *  &lt;demo:pageDisplayer name="ListPage" type="normal" &gt;
 *  &lt;/demo:pageDisplayer&gt;
 * </pre>
 * 
 * @author ryuu.kk
 *
 */
public class PageDisplayerTag extends TagSupport {

	/**
	 * 版本序列化ID
	 */
	private static final long serialVersionUID = -5562531288206750987L;

	/** 日志 **/
	private static Log log = LogFactory.getLog(MenuDisplayerTag.class);
	
    /** menu的默认Key **/
    public static final String PAGE_ID = "pageId";
    /** Lib的默认资源包 **/
    protected static ResourceBundle messages = ResourceBundle.getBundle("com.demo.webapp.ui.menu.taglib.LocalStrings");
	
	 //~ Instance fields ========================================================
    protected String name;
    /**可组合的地址**/
    protected String page;
    /** 指定Page对象名,该对象有page-config.xml中的<Pages><page></page></Pages>中配置 **/
    protected String type;
    /** 在何处打开目标 URL。 **/
    protected String target;
    protected String bundleKey;
    /** 标签自定义的本地化Key; 默认使用其他策略 **/
    protected String localeKey;
    protected String repository;
    protected String config = Displayer.DEFAULT_CONFIG;
    protected Displayer<PageComponent> pageDisplayer;
    protected ResourceBundle rb;// used to allow setting of ResourceBundle 
    							// from JSTL in EL tag
    //使用Spring's MessageSource for locale message;
    protected MessageSource messageSource;
    //Spring容器
    private ApplicationContext applicationContext; 
    
    //~ Methods ================================================================

	public void setConfig(String config) {
		if (log.isDebugEnabled()) {
			log.debug("setting config to: " + config);
		}
		this.config = config;
	}

	public String getConfig() {
		return config;
	}

	public void setBundle(String bundleKey) {
		this.bundleKey = bundleKey;
	}

	public String getBundle() {
		return bundleKey;
	}

	public void setLocale(String locale) {
		this.localeKey = locale;
	}

	public String getLocale() {
		return localeKey;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getRepository() {
		return repository;
	}

	public void setTarget(String target) {
		this.target = target;
	}

	public void setPage(String page) {
		this.page = page;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	/**
    * 这个方法设置Menu持久资源名称
    * 默认的使用{@link MenuDisplayerTag.MENU_REPOSITORY_KEY}
    * @param repository 持久资源名
    */
   public void setRepository(String repository) {
       this.repository = repository;
   }
       
   @SuppressWarnings("unchecked")
   public int doStartTag() throws JspException {
       if (repository == null) {
           repository = PageRepositoryResource.PAGE_REPOSITORY_KEY;
       }

       if (log.isDebugEnabled()) {
           log.debug("Looking for repository named '" + repository + "'");
       }

       //取得Menu的数据持久化仓库
       PageRepositoryResource rep = (PageRepositoryResource) pageContext.findAttribute(this.repository);

       if (rep == null) {
           throw new JspException(messages.getString("menurepository.not.found"));
       } 
       // 取得显示器Map对象
       DisplayerMapping displayerMapping = rep.getMapping();

       if (displayerMapping == null) {
           throw new JspException(messages.getString("displayer.mapping.not.found"));
       }
       Displayer<PageComponent> displayerInstance = (Displayer<PageComponent>) displayerMapping.get(this.name);
       
		if (displayerInstance == null) {
			// 创建新MenuDisplayer实例
			try {
				DisplayerConfig disConfig = null;
				List<DisplayerConfig> configList = displayerMapping.getDisplayerConfig();
				if (configList == null || configList.size() == 0) {
					throw new Exception(messages.getString("menurepository.displayer.conifg.not.fount"));
				}
				for (DisplayerConfig config : configList) {
					if (config.getName().equals(this.name)) {
						disConfig = config;
						break;
					}
				}
				// 不存在指定的配置信息
				if (disConfig == null) {
					throw new Exception(messages.getString("menurepository.displayer.conifg.not.fount"));
				}
				// 创建一个Displayer的新实例对象
				displayerInstance = (Displayer<PageComponent>) Class.forName(disConfig.getType()).newInstance();
				if (!displayerMapping.containsKey(this.name)) {
					displayerInstance.setConfig(disConfig.getConfig());
					displayerInstance.setName(disConfig.getName());
					displayerMapping.put(displayerInstance);
				}
				pageDisplayer = displayerInstance;
			} catch (Exception e) {
				throw new JspException(e.getMessage());
			}
		}
       //多语言资源Key
       if (bundleKey == null || "".equals(bundleKey)) {
           this.bundleKey = "org.apache.struts.action.MESSAGE";
       	//this.bundleKey = "com.demo.webapp.ui.MESSAGE";
       }
       
       // setup the displayerInstance
       // if the displayer is a MessageResourceMenuDisplayer
       // and a bundle is specified, then pass it the bundle (message resources) and
       // the locale
		if (displayerInstance instanceof MessageResourcesDisplayer) {
			MessageResourcesDisplayer<? extends Component> mrDisplayerInstance = (MessageResourcesDisplayer<?>) displayerInstance;

			Locale locale;
			if (localeKey == null) {
				// 默认使用Spring的Locale
				locale = LocaleContextHolder.getLocale();
				if (locale == null) {
					// 使用struts的Locale
					locale = (Locale) pageContext.findAttribute("org.apache.struts.action.LOCALE");
				}
				if (locale == null) {
					// 使用浏览器的Locale
					locale = pageContext.getRequest().getLocale();
				}
			} else {
				locale = (Locale) pageContext.findAttribute(localeKey);
			}
			// ~ 将不做Displayer的默认Locale进行修改 edit by ryuu.kk at 2012/02/20
			// ===========================================
			// mrDisplayerInstance.setLocale(locale);

			if (messageSource != null) {
				mrDisplayerInstance.setMessageBundle(messageSource);
			} else {
				// 使用bundleKey取得Bundle：
				// ①、Spring上下文取得;
				// ②、Struts2的对语言Bundle；
				// ③、字符串传入,Displayer自行处理
				if (applicationContext == null) {
					applicationContext = WebApplicationContextUtils.getWebApplicationContext(pageContext.getServletContext());
				}
				if (applicationContext != null && applicationContext.containsBean(bundleKey)) {
					try {
						messageSource = applicationContext.getBean(bundleKey, MessageSource.class);
						mrDisplayerInstance.setMessageBundle(messageSource);
					} catch (BeansException e) {
						log.error(e.getMessage());
					}
				} else {
					// 如果为空则无法取得Spring上下文、不是Spring项目
					Object resources = pageContext.findAttribute(bundleKey);
					if (resources == null) {
						// 取得ResourceBundle
						try {
							rb = ResourceBundle.getBundle(bundleKey, locale);
							mrDisplayerInstance.setMessageBundle(rb);
						} catch (MissingResourceException mre) {
							log.error(mre.getMessage());
						}
					} else {
						mrDisplayerInstance.setMessageBundle(resources);
					}
				}
			}
		}
       //将用户配置传递到Displayer中
       displayerInstance.setConfig(config);
       if (id != null) {
           pageContext.setAttribute(PAGE_ID, id);
       }

       displayerInstance.init(pageContext, displayerMapping);
      
       // 重写目标地址的打开方式
       if (target != null) {
    	   displayerInstance.setTarget(this.target);
       }
       
       //使用request取得Action
       //HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
       
       //~ Struts2 的配置===============================================================
       
	   PageSupport pageSupport = null;
		try {
			pageSupport = getPageSupport();
		} catch (PageSupportNotFoundException e) {
			log.error(e.getMessage());
			e.printStackTrace();
			//处理Body体内部分.
			return (EVAL_BODY_INCLUDE);
		}
	   
	   PageComponent pageComponent = null;
	   if (this.type != null) {
		   pageComponent = rep.getComponent(this.type);
	   }
	   if (pageComponent == null) {
		   pageComponent = new PageComponent();
	   }
	   
	   //TODO: TEST at 2012-02-25 防止同步问题
	   pageComponent = pageComponent.clone();
	   
	   pageComponent.setPageSupport(pageSupport);
	   
	   //使用标签的page属性,覆盖配置的page属性
	   if (this.page != null) {
		   pageComponent.setPage(page);
	   }
	   
       try {
    	   // 设置location值(当没有指定该值时,进行计算) 
           setPageLocation(pageComponent);
       } catch (MalformedURLException m) {
           log.error("Incorrect action or forward: " + m.getMessage());
           pageComponent.setLocation("#");
       } 
		try {
			displayerInstance.display(pageComponent);
		} catch (IOException e) {
			log.error(e.getMessage());
			throw new JspException(e);
		}
       //ValueStack valueStack = context.getValueStack();
       //displayerInstance.display(model);
       return (EVAL_BODY_INCLUDE);
   }
   
   /**
    * 这是Struts2的PageSupport取得方式,可扩展为其他方式取得
    * @return PageSupport 分页支持
    * @throws PageSupportNotFoundException 未找到PageSupport异常
    */
   protected PageSupport getPageSupport() throws PageSupportNotFoundException {
	   ActionContext context = ActionContext.getContext();
       Object currentAction = context.getActionInvocation().getAction();
       if (currentAction instanceof PageSupport) {
    	   return (PageSupport) currentAction;
       }
       //#"PageSupprot not found. This Action must be extends from PageSupport."
       throw new PageSupportNotFoundException();
   }
   public int doEndTag() throws JspException {
       pageDisplayer.end(pageContext);
       return (EVAL_PAGE);
   }

   public void release() {
       if (log.isDebugEnabled()) {
           log.debug("release() called");
       }

       this.bundleKey = null;
       this.config = Displayer.DEFAULT_CONFIG;
       this.localeKey = null;
       this.name = null;
       this.pageDisplayer = null;
       this.repository = null;
       this.rb = null;
   }
   
   /**
   * 当location为空时，设置location地址
   * 如果page不为空,则使用page地址作为location地址
   * 如果page也为空,则使用forward地址作为location地址
   * FIXME
   * 根据通配符${}查找相应数据
   *
   * @param menu Menu组件对象
   */
  protected void setPageLocation(ComponentBase base) throws MalformedURLException, JspException {
      HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
      setLocation(base);
      String url = base.getLocation();

      //检查是有存在EL通配符
      if ((url != null) && (url.indexOf("${") > -1)) {
          String queryString = null;

          if (url.indexOf("?") > -1) {
              queryString = url.substring(url.indexOf("?") + 1);
              url = url.substring(0, url.indexOf(queryString));
          }
          // variable is in the URL
          if (queryString != null) {
        	  base.setUrl(url + parseString(queryString, request));
          } else {
              // parse the URL, rather than the queryString
        	  base.setUrl(parseString(url, request).toString());
          }
      } else {
    	  base.setUrl(url);
      }

      HttpServletResponse response = (HttpServletResponse) pageContext.getResponse();
      if (base.getUrl() != null) {
    	  base.setUrl(response.encodeURL(base.getUrl()));
      }

      // do all contained menus
      List<ComponentBase> subMenus = base.getSubComponents(); 

      if (subMenus != null && subMenus.size() > 0) {
          for (ComponentBase subMenu : subMenus) {
              this.setPageLocation(subMenu);
          }
      }
  }

  protected void setLocation(ComponentBase page) throws MalformedURLException {
      // if the location attribute is null, then set it with a context relative page
      // attribute if it exists
      if (page.getLocation() == null) {
          try {
              if (page.getPage() != null) {
                  HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
                  page.setLocation(request.getContextPath() + getPage(page.getPage()));
                  HttpServletResponse response = (HttpServletResponse) pageContext.getResponse();
                  page.setLocation(response.encodeURL(page.getLocation()));
              } else if (page.getForward() != null) {
                  HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
                  page.setLocation(request.getContextPath() + getPage(page.getForward()));
                  HttpServletResponse response = (HttpServletResponse) pageContext.getResponse();
                  page.setLocation(response.encodeURL(page.getLocation()));
              }
          } catch (NoClassDefFoundError e) {
              if (page.getForward() != null) {
                  throw new MalformedURLException("forward '" + page.getForward() + "' invalid - no struts.jar");
              } 
          }
      }
  }

  /**
   * 返回page属性
   * 如果page没有设置"/",将page增加一个 "/"
   *
   * @param page page属性
   */
  protected String getPage(String page) {
      if (page.startsWith("/")) {
          return page;
      } else {
          page = "/" + page;
      }

      return page;
  }
  
  private StringBuffer parseString(String str, HttpServletRequest request) {
      StringBuffer sb = new StringBuffer();
      
      while (str.indexOf("${") >= 0) {
          sb.append(str.substring(0, str.indexOf("${")));

          String variable = str.substring(str.indexOf("${") + 2, str.indexOf("}"));
          //对page,request,session（如果有效的话）和application范围进行查找
          String value = String.valueOf(pageContext.findAttribute(variable)); 
          
          if (value == null) {
              //如果不存在,则查看请求参数
              value = request.getParameter(variable);
          }

          //value仍然为空,使用Struts2的值栈? no.
          if (value == null) {
              log.warn("Value for '" + variable +
                      "' not found in pageContext or as a request parameter");
          }

          sb.append(value);
          str = str.substring(str.indexOf("}") + 1, str.length());
      }

      return sb.append(str);
  }
}
